package com.wsleo.core.db;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.wsleo.common.utils.ConfigLoader;

/**
 * DBManager
 */
public class DBManager {

    private final static Log log = LogFactory.getLog(DBManager.class);
    private final static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
    private static DataSource dataSource;
    private static boolean show_sql = false;
    private final static String _PREFIX = "jdbc.";

    static {
        initDataSource(null);
    }

    /**
     * 初始化连接池
     * 
     * @param props
     * @param show_sql
     */
    private final static void initDataSource(Properties dbProperties) {
        try {
            Properties cp_props = db2C3p0Properties(dbProperties);
            dataSource = (DataSource) Class.forName(cp_props.getProperty("datasource")).newInstance();
            if (dataSource.getClass().getName().indexOf("c3p0") > 0) {
                // Disable JMX in C3P0
                System.setProperty("com.mchange.v2.c3p0.management.ManagementCoordinator",
                        "com.mchange.v2.c3p0.management.NullManagementCoordinator");
            }
            log.info("Using DataSource : " + dataSource.getClass().getName());
            BeanUtils.populate(dataSource, cp_props);

            testConnection();
        } catch (Exception e) {
            throw new DBException(e);
        }
    }
    private static Properties db2C3p0Properties(Properties dbProperties) {
        if (dbProperties == null) {
            dbProperties = ConfigLoader.getInstance().getDbProp();
        }
        
        Properties props = new Properties();
        for (Object key : dbProperties.keySet()) {
            String skey = (String) key;
            if (skey.startsWith(_PREFIX)) {
                String name = skey.substring(_PREFIX.length());
                props.put(name, dbProperties.getProperty(skey));
                if ("show_sql".equalsIgnoreCase(name)) {
                    show_sql = Boolean.toString(true).equalsIgnoreCase(
                            dbProperties.getProperty(skey));
                }
            }
        }
        return props;
    }
    
    private static void testConnection() {
        try {
            Connection conn = getConnection();
            DatabaseMetaData mdm = conn.getMetaData();
            log.info("Connected to " + mdm.getDatabaseProductName() + " " + mdm.getDatabaseProductVersion());
            closeConnection();
        } catch (SQLException e) {
            throw new DBException("Test Connection", e);
        }
    }

    /**
     * close dataSource
     */
    public final static void closeDataSource() {
        try {
            dataSource.getClass().getMethod("close").invoke(dataSource);
        } catch (NoSuchMethodException e) {
        } catch (Exception e) {
            log.error("Unabled to destroy DataSource!!! ", e);
        }
    }

    public final static Connection getConnection() throws SQLException {
        Connection conn = conns.get();
        if (conn == null || conn.isClosed()) {
            conn = dataSource.getConnection();
            conns.set(conn);
        }
        return (show_sql && !Proxy.isProxyClass(conn.getClass())) ? new _DebugConnection(
                conn).getConnection() : conn;
    }

    /**
     * close Connection
     */
    public final static void closeConnection() {
        Connection conn = conns.get();
        try {
            if (conn != null && !conn.isClosed()) {
                conn.setAutoCommit(true);
                conn.close();
            }
        } catch (SQLException e) {
            log.error("Unabled to close connection!!! ", e);
        }
        conns.set(null);
    }

    /**
     * debug connection mode : to show sql
     */
    private static class _DebugConnection implements InvocationHandler {

        private final static Log log = LogFactory.getLog(_DebugConnection.class);

        private Connection conn = null;

        public _DebugConnection(Connection conn) {
            this.conn = conn;
        }

        /**
         * 
         * @return Connection
         */
        public Connection getConnection() {
            return (Connection) Proxy.newProxyInstance(conn.getClass()
                    .getClassLoader(), conn.getClass().getInterfaces(), this);
        }

        public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
            try {
                String method = m.getName();
                if ("prepareStatement".equals(method) || "createStatement".equals(method))
                    log.info("[SQL] >>> " + args[0]);
                return m.invoke(conn, args);
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }

    }

}
